package com.recharge.aop;


import com.recharge.common.annotation.RedisCacheAble;
import com.recharge.common.util.RedisUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Aspect
@Component
public class RedisAbleAspect {

    @Autowired
    private RedisUtil redisServer;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.recharge.common.annotation.RedisCacheAble)")
    public void redisAblePointcut() {
    }



    /**
     * 配置通知（环绕通知）
     * ProceedingJoinPoint 是 JoinPoint(连接点) 的子类
     * JoinPoint 中有获取目标方法参数,目标签名)
     *
     * @param joinPoint 连接点
     * @return Object
     */
    @Around("redisAblePointcut()")
    public Object redisAdvice(ProceedingJoinPoint joinPoint) {
        try {
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            RedisCacheAble annotation = method.getAnnotation(RedisCacheAble.class);
            //查询
            Class returnType = signature.getReturnType();

            if (annotation != null) {
                //获取所属组
                String group = annotation.group();

                //获取所属值
                String key = annotation.key();

                Object returnVale = null;

                String sPel="";

                /**
                 * 查询操作
                 */
                if (annotation.query()) {
                    //解析spel 表达式，返回结果
                    sPel = generateKeyBySpEL(key, method, joinPoint.getArgs());

                    returnVale = redisServer.get(group + sPel);
                    if (returnVale == null) {
                        //继续执行目标方法
                        returnVale = joinPoint.proceed();
                        /**
                         * 解决缓存穿刺问题 （在数据库内没查询到数据时，在缓存中添加一个默认值）
                         */
                        if(returnVale==null){
                            redisServer.set(group + sPel, "default", annotation.expire());
                        }else {
                            redisServer.set(group + sPel, returnVale, annotation.expire());
                        }

                    }else if(returnVale instanceof String && String.valueOf(returnVale).equals("default")){
                        return null;
                    }else {
                        return returnVale;
                    }
                } else {
                    //继续执行目标方法
                    returnVale = joinPoint.proceed();
                    Object[] args = joinPoint.getArgs();
                    sPel = generateKeyBySpEL(key, method, joinPoint.getArgs());
                    redisServer.set(group + sPel, returnVale, annotation.expire());
                    return returnVale;
                }
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return null;
    }

    /**
     * 用于SpEL表达式解析.
     */
    private SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    /**
     * 解析SpEl表达式返回值
     *
     * @param spELString, method, args
     * @return String
     */
    public String generateKeyBySpEL(String spELString, Method method, Object[] args) {
        String[] paramNames = nameDiscoverer.getParameterNames(method);
        Expression expression = parser.parseExpression(spELString);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < args.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return expression.getValue(context).toString();
    }




}
